﻿using System.Reflection;
using ApplicationFrame.EntityFrameworkCore.Core;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using ShardingCore;
using ShardingCore.Core.ServiceProviders;
using ShardingCore.Core.ShardingConfigurations;
using Volo.Abp.Domain.Entities;
using Volo.Abp.EntityFrameworkCore;

namespace ApplicationFrame.EntityFrameworkCore
{
    /// <summary>
    /// EntityFrameworkCoreExtensions
    /// </summary>
    public static class EntityFrameworkCoreExtensions
    {
        /// <summary>
        ///  AddMyDbContext
        /// </summary>
        public static IServiceCollection AddMyDbContext<TDbContext>(this IServiceCollection services, Action<DbContextOptionsBuilder> optionAction) where TDbContext : BaseDbContext<TDbContext>
        {
            var action1 = optionAction;
            services.Configure<AbpDbContextOptions>(options =>
            {
                options.Configure<TDbContext>(dbConfContext =>
                {
                    if (action1 != null)
                        action1(dbConfContext.DbContextOptions);
                });
            });
            services.AddAbpDbContext<TDbContext>(options =>
            {
                options.AddDefaultRepositories();
            });

            return services;
        }

        /// <summary>
        /// AddMyShardingDbContext
        /// </summary>
        public static IServiceCollection AddMyShardingDbContext<TDbContext>(this IServiceCollection services, Action<DbContextOptionsBuilder> abpDbBuilder, Action<IShardingProvider, ShardingConfigOptions> shardingConfigBuilder)
            where TDbContext : BaseShardingDbContext<TDbContext>
        {
            var action1 = abpDbBuilder;
            var action2 = shardingConfigBuilder;
            SetShardingEntityType<TDbContext>();
            services.AddSingleton<ShardingStrategyFactory>();
            services.AddMyDbContext<TDbContext>(dbOptionBuilder => action1(dbOptionBuilder));
            services.AddShardingDbContext<TDbContext>(ServiceLifetime.Scoped, ServiceLifetime.Scoped)
                .UseRouteConfig(shardingRouteConfig =>
                {
                    // 注册所有分表实体的路由
                    foreach (var dic in ShardingDic)
                    {
                        var routeType = typeof(GenericShardingRoute<,>).MakeGenericType(dic.Key, dic.Value.Item2);
                        shardingRouteConfig.AddShardingTableRoute(routeType);
                    }
                })
                .UseConfig((shardingProvider, shardingConfig) => action2(shardingProvider, shardingConfig))
                .AddShardingCore();
            return services;
        }

        #region private methods
        internal static Dictionary<Type, (Attribute, Type)> ShardingDic { get; } = new Dictionary<Type, (Attribute, Type)>();

        /// <summary>
        /// 获取同DbContext下IEntity能分表的实体类型
        /// </summary>
        private static void SetShardingEntityType<TDbContext>()
        {
            var entityTypes = typeof(TDbContext).Assembly.GetTypes()
                       .Where(t => t.IsClass && !t.IsAbstract && !t.IsGenericType && t.IsAssignableTo<IEntity>() && !t.IsInterface);
            foreach (var type in entityTypes)
            {
                Type shardingKeyType = null;
                var timeAttr = type.GetCustomAttribute<TimeShardingAttribute>();
                if (timeAttr != null)
                {
                    shardingKeyType = type.GetProperty(timeAttr.ShardingProperty)?.PropertyType;
                    if (shardingKeyType != null && (shardingKeyType == typeof(DateTime) || shardingKeyType == typeof(DateTimeOffset)))
                    {
                        ShardingDic.TryAdd(type, (timeAttr, shardingKeyType));
                        continue;
                    }
                }

                var hashAttr = type.GetCustomAttribute<HashShardingAttribute>();
                if (hashAttr != null)
                {
                    shardingKeyType = type.GetProperty(hashAttr.ShardingProperty)?.PropertyType;
                    ShardingDic.TryAdd(type, (hashAttr, shardingKeyType));
                    continue;
                }

                var rangeAttr = type.GetCustomAttribute<RangeShardingAttribute>();
                if (rangeAttr != null)
                {
                    shardingKeyType = type.GetProperty(rangeAttr.ShardingProperty)?.PropertyType;
                    if (shardingKeyType != null && (shardingKeyType == typeof(long) || shardingKeyType == typeof(ulong) || shardingKeyType == typeof(int) || shardingKeyType == typeof(uint)))
                    {
                        ShardingDic.TryAdd(type, (rangeAttr, shardingKeyType));
                        continue;
                    }
                }
            }
        }
        #endregion
    }
}

